﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Office.Project.EPMAuditing.Library;
using System.Globalization;
using System.Reflection;

namespace Microsoft.Office.Project.EPMAuditing.ReceiverService
{
    public partial class ReceiverServices : ServiceBase
    {
        [DllImport("ADVAPI32.DLL", EntryPoint = "SetServiceStatus")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool SetServiceStatus(IntPtr hServiceStatus, ServiceStatus lpServiceStatus);

        private ServiceStatus _serviceStatus;
        private Thread _UDPListenerWorkerThread = null;
        private Thread _AuditWriterWorkerThread = null;
        private ManualResetEvent _pause = new ManualResetEvent(false);

        private bool _executeRefreshListenerList = false;
        private bool _startNewFile = false;

        private Tracing _tracing = null;
        private int _threadOperationTimeoutSeconds = 10;

        public ReceiverServices()
        {
            InitializeComponent();
            InitializeData();
        }

        private void InitializeData()
        {
            // Read configuration file
            string fullPath = Assembly.GetExecutingAssembly().Location;
            EPMAuditingConfigFile configFile = new EPMAuditingConfigFile(string.Format(CultureInfo.InvariantCulture, "{0}{1}", 
                fullPath.Substring(0, fullPath.LastIndexOf(@"\", StringComparison.OrdinalIgnoreCase) + 1),
                EPMAuditingConfigFile.DEFAULT_CONFIG_FILENAME));
            EPMAuditingConfig config = configFile.AuditingConfiguration;
            if (config == null)
                return;

            _threadOperationTimeoutSeconds = config.ThreadOperationTimeoutSeconds;

        }

        #region Public Methods

        /// <summary>
        /// Method for the worker thread to query if the RefreshListenerList custom command has been invoked.
        /// </summary>
        /// <returns>True if RefreshListenerList has been triggered.</returns>
        public bool GetRefreshListenerListCallback()
        {
            return _executeRefreshListenerList;
        }

        /// <summary>
        /// Method for the worker thread set the status of the RefreshListenerList flag.  Usually called to clear
        /// the thread after reacting to it being set.
        /// </summary>
        /// <param name="syncNow">Boolean value to set RefreshListenerList with.</param>
        public void SetRefreshListenerListCallback(bool refreshNow)
        {
            _executeRefreshListenerList = refreshNow;
        }

        /// <summary>
        /// Method for the worker thread to query if the StartNewFile custom command has been invoked.
        /// </summary>
        /// <returns>True if StartNewFile has been triggered.</returns>
        public bool GetStartNewFileCallback()
        {
            return _startNewFile;
        }

        /// <summary>
        /// Method for the worker thread set the status of the StartNewFile flag.  Usually called to clear
        /// the thread after reacting to it being set.
        /// </summary>
        /// <param name="syncNow">Boolean value to set StartNewFile with.</param>
        public void SetStartNewFileCallback(bool startNow)
        {
            _startNewFile = startNow;
        }

        /// <summary>
        /// Property to return the active tracing service
        /// </summary>
        public Tracing TracePtr
        {
            get
            {
                return _tracing;
            }
        }

        #endregion
        
        #region Overriden Service Methods

        /// <summary>
        /// Receives the Start command from the Service Control Manager.  Start the tracing 
        /// and spawns the worker thread.
        /// </summary>
        /// <param name="args">Optional. Set the tracing level 1-4.</param>
        protected override void OnStart(string[] args)
        {
            if (args.Length == 1)
                _tracing = new Tracing((TraceLevel)Convert.ToInt32(args[0], CultureInfo.InvariantCulture));
            else
                _tracing = new Tracing(TraceLevel.Info);

            IntPtr handle = this.ServiceHandle;
            _serviceStatus.currentState = (int)ServiceState.StartPending;
            SetServiceStatus(handle, _serviceStatus);
            AuditingWorkerThreads awt = new AuditingWorkerThreads(_tracing, _pause, 
                GetRefreshListenerListCallback, SetRefreshListenerListCallback,
                GetStartNewFileCallback, SetStartNewFileCallback);

            // Start a separate thread for receiving UDP messages.
            if ((_UDPListenerWorkerThread == null) ||
                ((_UDPListenerWorkerThread.ThreadState &
                 (System.Threading.ThreadState.Unstarted | System.Threading.ThreadState.Stopped)) != 0))
            {
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Starting the UDPListenerWorkerMethod thread");
                _UDPListenerWorkerThread = new Thread(new ThreadStart(awt.UDPListenerWorkerMethod));
                _UDPListenerWorkerThread.Priority = ThreadPriority.AboveNormal;
                _UDPListenerWorkerThread.Start();
                DateTime timeoutTime = DateTime.Now.AddSeconds(_threadOperationTimeoutSeconds);
                bool updListenerWorkerThreadWaiting = false;
                while (!updListenerWorkerThreadWaiting)
                {
                    _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, string.Format(CultureInfo.InvariantCulture, "Thread State: {0}", 
                        _UDPListenerWorkerThread.ThreadState.ToString()));
                    if (_UDPListenerWorkerThread.ThreadState == System.Threading.ThreadState.Running)
                    {
                        updListenerWorkerThreadWaiting = true;
                    }
                    else
                    {
                        Thread.Sleep(500);

                        if (DateTime.Now > timeoutTime)
                        {
                            updListenerWorkerThreadWaiting = true;
                            _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Timed out waiting to start UDP Listener Worker Thread");
                        }
                    }
                }

            }
            if (_UDPListenerWorkerThread != null)
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "UDP Worker Thread state = {0}", _UDPListenerWorkerThread.ThreadState.ToString()));



            // Start a separate thread for receiving Audit Writer Worker.
            if ((_AuditWriterWorkerThread == null) ||
                ((_AuditWriterWorkerThread.ThreadState &
                 (System.Threading.ThreadState.Unstarted | System.Threading.ThreadState.Stopped)) != 0))
            {
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Starting the AuditWriterWorkerMethod thread");
                _AuditWriterWorkerThread = new Thread(new ThreadStart(awt.AuditWriterWorkerMethod));
                _AuditWriterWorkerThread.Start();

                DateTime timeoutTime = DateTime.Now.AddSeconds(_threadOperationTimeoutSeconds);
                bool AuditWriterWorkerThreadWaiting = false;
                while (!AuditWriterWorkerThreadWaiting)
                {
                    if (_AuditWriterWorkerThread.ThreadState == System.Threading.ThreadState.Running)
                    {
                        AuditWriterWorkerThreadWaiting = true;
                    }
                    else
                    {
                        Thread.Sleep(500);

                        if (DateTime.Now > timeoutTime)
                        {
                            AuditWriterWorkerThreadWaiting = true;
                            _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Timed out waiting to start Audit Writer Worker Thread");
                        }
                    }
                }


            }
            if (_AuditWriterWorkerThread != null)
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "Audit Writer Worker Thread state = {0}", _AuditWriterWorkerThread.ThreadState.ToString()));

            

            _serviceStatus.currentState = (int)ServiceState.Running;
            SetServiceStatus(handle, _serviceStatus);
            _pause.Set();
        }

        /// <summary>
        /// Receives the Stop command from the Service Control Manager.  Signals the worker thread to exit.
        /// Waits for the thread to exit.
        /// </summary>
        protected override void OnStop()
        {
            this.RequestAdditionalTime(4000);
            // Signal the worker thread to exit.
            if ((_UDPListenerWorkerThread != null) && (_UDPListenerWorkerThread.IsAlive))
            {
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Stopping the UDP Listener Worker Thread");
                _pause.Reset();

                DateTime timeoutTime = DateTime.Now.AddSeconds(_threadOperationTimeoutSeconds);
                bool updListenerWorkerThreadWaiting = false;
                while (!updListenerWorkerThreadWaiting)
                {
                    if (_UDPListenerWorkerThread.ThreadState == System.Threading.ThreadState.WaitSleepJoin)
                    {
                        updListenerWorkerThreadWaiting = true;
                        _UDPListenerWorkerThread.Abort();
                    }
                    else
                    {
                        Thread.Sleep(500);

                        if (DateTime.Now > timeoutTime)
                        {
                            updListenerWorkerThreadWaiting = true;
                            _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Timed out waiting to stop UDP Listener Worker Thread");
                        }
                    }
                }
            }
            if (_UDPListenerWorkerThread != null)
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "OnStop UDP Listener Worker Thread state = {0}", _UDPListenerWorkerThread.ThreadState.ToString()));



            // Signal the worker thread to exit.
            if ((_AuditWriterWorkerThread != null) && (_AuditWriterWorkerThread.IsAlive))
            {
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Stopping the Audit Writer worker thread");
                _pause.Reset();
                DateTime timeoutTime = DateTime.Now.AddSeconds(_threadOperationTimeoutSeconds);
                bool AuditWriterWorkerThreadWaiting = false;
                while (!AuditWriterWorkerThreadWaiting)
                {
                    if (_AuditWriterWorkerThread.ThreadState == System.Threading.ThreadState.WaitSleepJoin)
                    {
                        _AuditWriterWorkerThread.Abort();
                        AuditWriterWorkerThreadWaiting = true;
                    }
                    else
                    {
                        Thread.Sleep(500);

                        if (DateTime.Now > timeoutTime)
                        {
                            AuditWriterWorkerThreadWaiting = true;
                            _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Timed out waiting to stop Audit Writer Worker Thread");
                        }
                    }
                }
            }
            if (_AuditWriterWorkerThread != null)
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "OnStop Audit Writer Worker Thread state = {0}", _AuditWriterWorkerThread.ThreadState.ToString()));

            // Indicate a successful exit.
            this.ExitCode = 0;
        }

        /// <summary>
        /// Sends a request to the worker thread to pause.
        /// </summary>
        protected override void OnPause()
        {
            // Pause the worker thread.
            if ((_UDPListenerWorkerThread != null) &&
                (_UDPListenerWorkerThread.IsAlive) &&
                ((_UDPListenerWorkerThread.ThreadState &
                 (System.Threading.ThreadState.Suspended | System.Threading.ThreadState.SuspendRequested)) == 0))
            {
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Pausing the UDP Listener worker thread");
                _pause.Reset();

                DateTime timeoutTime = DateTime.Now.AddSeconds(_threadOperationTimeoutSeconds);
                bool updListenerWorkerThreadWaiting = false;
                while (!updListenerWorkerThreadWaiting)
                {
                    if (_UDPListenerWorkerThread.ThreadState == System.Threading.ThreadState.WaitSleepJoin)
                    {
                        updListenerWorkerThreadWaiting = true;
                    }
                    else
                    {
                        Thread.Sleep(500);

                        if (DateTime.Now > timeoutTime)
                        {
                            updListenerWorkerThreadWaiting = true;
                            _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Timed out waiting to pause UDP Listener Worker Thread");
                        }
                    }
                }
            }
            if (_UDPListenerWorkerThread != null)
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "OnPause UDP Listener Worker thread state = {0}", _UDPListenerWorkerThread.ThreadState.ToString()));
            
            
            // Pause the worker thread.
            if ((_AuditWriterWorkerThread != null) &&
                (_AuditWriterWorkerThread.IsAlive) &&
                ((_AuditWriterWorkerThread.ThreadState &
                 (System.Threading.ThreadState.Suspended | System.Threading.ThreadState.SuspendRequested)) == 0))
            {
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Pausing the service Audit Writer Worker Thread");
                _pause.Reset();
                DateTime timeoutTime = DateTime.Now.AddSeconds(_threadOperationTimeoutSeconds);
                bool AuditWriterWorkerThreadWaiting = false;
                while (!AuditWriterWorkerThreadWaiting)
                {
                    if (_AuditWriterWorkerThread.ThreadState == System.Threading.ThreadState.WaitSleepJoin)
                    {
                        AuditWriterWorkerThreadWaiting = true;
                    }
                    else
                    {
                        Thread.Sleep(500);

                        if (DateTime.Now > timeoutTime)
                        {
                            AuditWriterWorkerThreadWaiting = true;
                            _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Timed out waiting to pause Audit Writer Worker Thread");
                        }
                    }
                }
            }

            if (_AuditWriterWorkerThread != null)
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "OnPause Audit Writer Worker Thread state = {0}", _AuditWriterWorkerThread.ThreadState.ToString()));
        }

        /// <summary>
        /// Sends a request to the worker thread to pause.
        /// </summary>
        protected override void OnContinue()
        {
            // Signal the worker thread to resume.
            if ((_UDPListenerWorkerThread != null) &&
                ((_UDPListenerWorkerThread.ThreadState &
                 (System.Threading.ThreadState.Suspended | System.Threading.ThreadState.SuspendRequested)) != 0))
            {
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Resuming the UDP Listener worker thread");
                _pause.Set();
                DateTime timeoutTime = DateTime.Now.AddSeconds(_threadOperationTimeoutSeconds);
                bool updListenerWorkerThreadWaiting = false;
                while (!updListenerWorkerThreadWaiting)
                {
                    if (_UDPListenerWorkerThread.ThreadState == System.Threading.ThreadState.Running)
                    {
                        updListenerWorkerThreadWaiting = true;
                    }
                    else
                    {
                        Thread.Sleep(500);

                        if (DateTime.Now > timeoutTime)
                        {
                            updListenerWorkerThreadWaiting = true;
                            _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Timed out waiting to continue UDP Listener Worker Thread");
                        }
                    }
                }
            }
            if (_UDPListenerWorkerThread != null)
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "OnContinue UDP Listener thread state = {0}", _UDPListenerWorkerThread.ThreadState.ToString()));
            
            
            // Signal the worker thread to resume.
            if ((_AuditWriterWorkerThread != null) &&
                ((_AuditWriterWorkerThread.ThreadState &
                 (System.Threading.ThreadState.Suspended | System.Threading.ThreadState.SuspendRequested)) != 0))
            {
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Resuming the Audit Writer Worker Thread");
                _pause.Set();

                DateTime timeoutTime = DateTime.Now.AddSeconds(_threadOperationTimeoutSeconds);
                bool AuditWriterWorkerThreadWaiting = false;
                while (!AuditWriterWorkerThreadWaiting)
                {
                    if (_AuditWriterWorkerThread.ThreadState == System.Threading.ThreadState.Running)
                    {
                        AuditWriterWorkerThreadWaiting = true;
                    }
                    else
                    {
                        Thread.Sleep(500);

                        if (DateTime.Now > timeoutTime)
                        {
                            AuditWriterWorkerThreadWaiting = true;
                            _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Timed out waiting to continue Audit Writer Worker Thread");
                        }
                    }
                }

            }
            if (_AuditWriterWorkerThread != null)
                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "OnContinue Audit Writer Worker Thread state = {0}", _UDPListenerWorkerThread.ThreadState.ToString()));
        }

        /// <summary>
        /// Handle the custom command coming to the EPMAuditing service.
        /// </summary>
        /// <param name="command">CommandID for the desired command.</param>
        protected override void OnCustomCommand(int command)
        {
            _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "Custom command received = {0}", command.ToString(CultureInfo.InvariantCulture)));

            switch (command)
            {
                case (int)SimpleServiceCustomCommand.ExecuteRefreshListenerList:
                    _executeRefreshListenerList = true;
                    break;

                case (int)SimpleServiceCustomCommand.StartNewFile:
                    _startNewFile = true;
                    break;

                case (int)SimpleServiceCustomCommand.CheckWorker:
                    _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "OnCustomCommand Worker thread state = {0}", _UDPListenerWorkerThread.ThreadState.ToString()));
                    break;

                default:
                    break;
            }
        }

        #endregion
    }
}
